package cn.roisrosy.middleware.db.router.dynamic;

import cn.roisrosy.middleware.db.router.DBContextHolder;
import cn.roisrosy.middleware.db.router.annotation.DBRouterStrategy;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/***
 * Mybatis 拦截器,通过对SQL 语句的拦截处理，修改分表信息
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicMybatisPlugin implements Interceptor {
    private Logger logger = LoggerFactory.getLogger(DynamicMybatisPlugin.class);

    private Pattern pattern = Pattern.compile("(from|into|update)[\\s]{1,}(\\w{1,})", Pattern.CASE_INSENSITIVE);


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("=======================开始执行Mybatis拦截器===================");
        //获取StatementHandler
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        //使用Mybatis的MetaObject工具类来创建一个用于操作statementHandler对象属性的实例，这样可以很方便地对statementHandler对象的属性进行操作和管理
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        //从 StatementHandler 对象的内部委托对象中获取 MappedStatement 对象，这个 MappedStatement 对象包含了当前正在处理的 SQL 语句的各种信息，比如 ID、SQL 内容、参数映射等。通常，这样的操作是为了在运行时获得和处理当前正在执行的 SQL 语句的相关信息
        //在 MyBatis 中，delegate 是 StatementHandler 中的一个内部对象，用于处理底层的 SQL 语句执行
        //MappedStatement 是 MyBatis 中用于表示映射语句（即在映射文件中定义的 SQL 语句）的类。它包含了 SQL 语句的各种信息，比如 ID、SQL 语句、参数映射等。
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        logger.info("============="+"获取映射语句类:{}",mappedStatement+"===================");
        //获取自定义注解判断是否进行分表操作;
        String id = mappedStatement.getId();
        logger.info("==============="+"拦截到的sqlId:{}", id +"========================");
        String className = id.substring(0, id.lastIndexOf("."));
        logger.info("==============="+"拦截到的类名:{}", className +"========================");
        //通过反射拿到该该方法
        Class<?> clazz = Class.forName(className);
        //拿到该方法饿注解
        DBRouterStrategy dbRouterStrategy= clazz.getAnnotation(DBRouterStrategy.class);
        logger.info("==============="+"拦截到的该方法的注解:{}", dbRouterStrategy +"========================");
        //如果该注解为空或者注解分表字段是false则直接跳过
        if (null == dbRouterStrategy || !dbRouterStrategy.splitTable()){
            return invocation.proceed();
        }
        // 获取SQL
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        logger.info("==============="+"拦截到的sql语句:{}", sql +"========================");
        // 替换SQL表名
        Matcher matcher = pattern.matcher(sql);
        String tableName = null;
        if (matcher.find()) {
            tableName = matcher.group().trim();
            logger.info("==============="+"拦截到的表名:{}", tableName +"========================");
        }
        assert null != tableName;
        String replaceSql = matcher.replaceAll(tableName + "_" + DBContextHolder.getTbKey());
        logger.info("==============="+"修改后的sql:{}", replaceSql +"========================");
        // 通过反射修改SQL语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        logger.info("==============="+"找到执行sql的方法", field +"========================");
        field.setAccessible(true);
        field.set(boundSql, replaceSql);
        field.setAccessible(false);
        return invocation.proceed();
    }
}
